package com.springboot.dubbo.cluster;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.apache.dubbo.common.Version;
import org.apache.dubbo.common.utils.NetUtils;
import org.apache.dubbo.rpc.Invocation;
import org.apache.dubbo.rpc.Invoker;
import org.apache.dubbo.rpc.Result;
import org.apache.dubbo.rpc.RpcContext;
import org.apache.dubbo.rpc.RpcException;
import org.apache.dubbo.rpc.cluster.Directory;
import org.apache.dubbo.rpc.cluster.LoadBalance;
import org.apache.dubbo.rpc.cluster.support.AbstractClusterInvoker;

import com.alibaba.dubbo.common.Constants;
import com.springboot.common.util.StringUtil;

import lombok.extern.log4j.Log4j2;

/**
 * 自定义集群invoker
 *
 * @author supu
 * @date 2019-08-07 9:49
 **/
@Log4j2
public class CustomClusterInvoker<T> extends AbstractClusterInvoker<T> {
    public static final String BIND_IP_KEY = "bind_ip_key";

    public CustomClusterInvoker(Directory<T> directory) {
        super(directory);
    }

    @Override
    protected Result doInvoke(Invocation invocation, List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {
        List<Invoker<T>> copyinvokers = invokers;
        checkInvokers(copyinvokers, invocation);
        int len = getUrl().getMethodParameter(invocation.getMethodName(), Constants.RETRIES_KEY, Constants.DEFAULT_RETRIES) + 1;
        if (len <= 0) {
            len = 1;
        }
        // retry loop.
        RpcException le = null;
        // last exception.
        List<Invoker<T>> invoked = new ArrayList<>(copyinvokers.size());
        // invoked invokers.
        Set<String> providers = new HashSet<>(len);
        for (int i = 0; i < len; i++) {
            //Reselect before retry to avoid a change of candidate `invokers`.
            //NOTE: if `invokers` changed, then `invoked` also lose accuracy.
            if (i > 0) {
                checkWhetherDestroyed();
                copyinvokers = list(invocation);
                // check again
                checkInvokers(copyinvokers, invocation);
            }
            Invoker<T> invoker = getInvokerByParam(loadbalance,invocation,copyinvokers,invoked,invokers);
            invoked.add(invoker);
            RpcContext.getContext().setInvokers((List) invoked);
            try {
                Result result = invoker.invoke(invocation);
                if (le != null && log.isWarnEnabled()) {
                    log.warn("Although retry the method " + invocation.getMethodName()
                            + " in the service " + getInterface().getName()
                            + " was successful by the provider " + invoker.getUrl().getAddress()
                            + ", but there have been failed providers " + providers
                            + " (" + providers.size() + "/" + copyinvokers.size()
                            + ") from the registry " + directory.getUrl().getAddress()
                            + " on the consumer " + NetUtils.getLocalHost()
                            + " using the dubbo version " + Version.getVersion() + ". Last error is: "
                            + le.getMessage(), le);
                }
                return result;
            } catch (RpcException e) {
                if (e.isBiz()) {
                    // biz exception.
                    throw e;
                }
                le = e;
            } catch (Throwable e) {
                le = new RpcException(e.getMessage(), e);
            } finally {
                providers.add(invoker.getUrl().getAddress());
            }
        }
        throw new RpcException(le != null ? le.getCode() : 0, "Failed to invoke the method "
                + invocation.getMethodName() + " in the service " + getInterface().getName()
                + ". Tried " + len + " times of the providers " + providers
                + " (" + providers.size() + "/" + copyinvokers.size()
                + ") from the registry " + directory.getUrl().getAddress()
                + " on the consumer " + NetUtils.getLocalHost() + " using the dubbo version "
                + Version.getVersion() + ". Last error is: "
                + (le != null ? le.getMessage() : ""), le != null && le.getCause() != null ? le.getCause() : le);
    }

    /**
     * 根据参数动态获取invoker
     *
     * @param loadbalance
     * @param invocation
     * @param copyinvokers
     * @param invoked
     * @param originInvokers
     * @return
     */
    private Invoker<T> getInvokerByParam(LoadBalance loadbalance, Invocation invocation, List<Invoker<T>> copyinvokers, List<Invoker<T>> invoked, List<Invoker<T>> originInvokers) {
        // 获取消费者处设置的参数
        String host = RpcContext.getContext().getAttachment(BIND_IP_KEY);
        if (StringUtil.isEmpty(host)) {
            return select(loadbalance, invocation, copyinvokers, invoked);
        }
        // 移除绑定的参数
        RpcContext.getContext().removeAttachment(BIND_IP_KEY);
        for (Invoker<T> invoker : originInvokers) {
            if (host.equals(invoker.getUrl().getHost())) {
                // 获取到了匹配的host的invoker
                return invoker;
            }
        }

        return select(loadbalance, invocation, copyinvokers, invoked);
    }
}
